package com.example.kotlinExample.service

import cn.hutool.core.date.DateUtil.today
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.redis.connection.BitFieldSubCommands
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Service
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.*


/**
 *
 *
 * @author liliang
 */
@Service
class UserSignService {

    @Autowired
    lateinit var redisTemplate: RedisTemplate<Any, Any>

    /**
     * 用户签到
     *
     * @param uid  用户ID
     * @param date 日期
     * @return 之前的签到状态
     */
    fun doSign(uid: Int, date: LocalDate): Boolean? {
        val offset = date.dayOfMonth - 1
        return redisTemplate.opsForValue().setBit(buildSignKey(uid, date), offset.toLong(), true)
    }

    /**
     * 检查用户是否签到
     *
     * @param uid  用户ID
     * @param date 日期
     * @return 当前的签到状态
     */
    fun checkSign(uid: Int, date: LocalDate): Boolean? {
        val offset = date.dayOfMonth - 1
        return redisTemplate.opsForValue().getBit(buildSignKey(uid, date), offset.toLong())
    }

    /**
     * 获取用户签到次数
     *
     * @param uid  用户ID
     * @param date 日期
     * @return 当前的签到次数
     */
    fun getSignCount(uid: Int, date: LocalDate) = redisTemplate.execute {
        it.bitCount(buildSignKey(uid, date).toByteArray())
    }

    /**
     * 获取当月首次签到日期
     *
     * @param uid  用户ID
     * @param date 日期
     * @return 首次签到日期
     */
    fun getFirstSignDate(uid: Int, date: LocalDate): LocalDate? {
        val pos: Long? = redisTemplate.execute{it.bitPos(buildSignKey(uid, date).toByteArray(), true)}
        pos?.let {
            return if (pos < 0) {
                null
            } else date.withDayOfMonth((pos + 1).toInt())
        }
        return null
    }

    /**
     * 获取当月签到情况
     *
     * @param uid  用户ID
     * @param date 日期
     * @return Key为签到日期，Value为签到状态的Map
     */
    fun getSignInfo(uid: Int, date: LocalDate): Map<String, Boolean>? {
        val unsigned = BitFieldSubCommands.BitFieldType.unsigned(date.lengthOfMonth())
        val commands = BitFieldSubCommands.create().get(unsigned).valueAt(0L)
        val signMap: MutableMap<String, Boolean> = HashMap(date.dayOfMonth)
        val list = redisTemplate.opsForValue().bitField(buildSignKey(uid, date), commands)
        if (list != null && list.size > 0) {
            // 由低位到高位，为0表示未签，为1表示已签
            var v = if (list[0] == null) 0 else list[0]!!
            for (i in date.lengthOfMonth() downTo 1) {
                val d = date.withDayOfMonth(i)
                signMap["yyyy-MM-dd".formatDate(d)] = v shr 1 shl 1 != v
                v = v shr 1
            }
        }
        println("当月签到情况：")
        val signInfo: Map<String, Boolean> = TreeMap<String, Boolean>(signMap)
        for ((key, value) in signInfo) {
            println(key + ": " + if (value) "√" else "-")
        }
        return signMap
    }


    /**
     * 获取当月连续签到次数
     *
     * @param uid  用户ID
     * @param date 日期
     * @return 当月连续签到次数
     */
    fun getContinuousSignCount(uid: Int, date: LocalDate): Long {
        val unsigned = BitFieldSubCommands.BitFieldType.unsigned(date.dayOfMonth)
        val commands = BitFieldSubCommands.create().get(unsigned).valueAt(0L)
        var signCount = 0
        val list = redisTemplate.opsForValue().bitField(buildSignKey(uid, date), commands)
        if (list != null && list.size > 0) {
            // 取低位连续不为0的个数即为连续签到次数，需考虑当天尚未签到的情况
            var v = if (list[0] == null) 0 else list[0]!!
            for (i in 0 until date.dayOfMonth) {
                if (v shr 1 shl 1 == v) {
                    // 低位为0且非当天说明连续签到中断了
                    if (i > 0) break
                } else {
                    signCount += 1
                }
                v = v shr 1
            }
        }
        return signCount.toLong()
    }



    private fun formatDate(date: LocalDate): String {
        return "yyyyMM".formatDate(date)
    }

    private fun String.formatDate(date: LocalDate): String {
        return date.format(DateTimeFormatter.ofPattern(this))
    }

    private fun buildSignKey(uid: Int, date: LocalDate): String {
        return String.format("u:sign:%d:%s", uid, formatDate(date))
    }

}